package org.datascooter.utils;

import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.CharBuffer;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.logging.Logger;

import org.datascooter.bundle.EntityBundle;
import org.datascooter.bundle.attribute.PersistReferenceAttribute;
import org.datascooter.exception.DataScooterException;
import org.datascooter.meta.MetaColumn;
import org.datascooter.meta.MetaLink;
import org.datascooter.meta.MetaTable;

/**
 * Contains a set of utility methods
 * 
 * @author nemo
 */
public final class LangUtils {

	private static final Random RANDOM = new Random();
	protected static final String SET = "set";
	protected static final String GET = "get";
	protected static final String ADD = "add";
	protected static final String PUT = "put";
	protected static final String REMOVE = "remove";
	protected static final String DELETE = "delete";
	private static int indexCount;

	private LangUtils() {
	}

	public static StringBuilder readFile(File file) {
		try {
			if (file.exists()) {
				CharBuffer buffer = CharBuffer.allocate(10000);
				StringBuilder builder = new StringBuilder();
				FileReader reader = new FileReader(file);
				buffer.clear();
				int a = 0;
				while ((a = reader.read(buffer)) > 0) {
					builder.append(buffer.array(), 0, a);
					buffer.clear();
				}
				reader.close();
				return builder;
			} else {
				return null;
			}
		} catch (Throwable e) {
			return null;
		}
	}

	public static List<Object[]> sortRowsByReferences(MetaTable table) {
		List<Object[]> sorted = new ArrayList<Object[]>();
		for (MetaLink link : table.getSelfKeys()) {
			int fromIndex = -1;
			int toIndex = -1;
			int count = 0;
			for (MetaColumn column : table.getColumns()) {
				if (column.COLUMN_NAME.equalsIgnoreCase(link.FKCOLUMN_NAME)) {
					toIndex = count;
				} else if (column.COLUMN_NAME.equalsIgnoreCase(link.PKCOLUMN_NAME)) {
					fromIndex = count;
				}
				count++;
			}
			if (fromIndex < 0 || toIndex < 0) {
				continue;
			}
			Set<Object> idSet = new HashSet<Object>();
			List<Object[]> unsorted = table.getData();
			Iterator<Object[]> iterator = unsorted.iterator();
			while (iterator.hasNext()) {
				Object[] array = iterator.next();
				if (array[toIndex] == null) {
					sorted.add(array);
					idSet.add(array[fromIndex]);
					iterator.remove();
				}
			}
			while (unsorted.size() > 0) {
				iterator = unsorted.iterator();
				while (iterator.hasNext()) {
					Object[] array = iterator.next();
					Object to = array[toIndex];
					if (idSet.contains(to)) {
						sorted.add(array);
						idSet.add(array[fromIndex]);
						iterator.remove();
					}
				}
			}
		}
		if (sorted.size() == 0 && table.getData().size() > 0) {
			return table.getData();
		}
		return sorted;
	}

	public static List<MetaTable> sortTablesByReferences(List<MetaTable> tables) {
		List<MetaTable> sorted = new ArrayList<MetaTable>();
		Set<String> namesSet = new HashSet<String>();
		Set<String> sortedSet = new HashSet<String>();
		Iterator<MetaTable> iterator = tables.iterator();
		while (iterator.hasNext()) {
			MetaTable next = iterator.next();
			namesSet.add(next.getName());
			if (next.getImportedKeys().size() == 0) {
				sorted.add(next);
				sortedSet.add(next.getName());
				iterator.remove();
			}
		}
		int size = tables.size();
		while (tables.size() > 0) {
			iterator = tables.iterator();
			while (iterator.hasNext()) {
				MetaTable next = iterator.next();
				boolean noneExistsKey = false;
				for (MetaLink link : next.getImportedKeys()) {
					if (!namesSet.contains(link.PKTABLE_NAME)) {
						continue;
					}
					if (link.PKTABLE_NAME.equals(next.getName())) {
						next.addSelfKey(link);
					}
					if ((!sortedSet.contains(link.PKTABLE_NAME)) && (!link.PKTABLE_NAME.equals(next.getName()))) {
						noneExistsKey = true;
						break;
					}
				}
				if (!noneExistsKey) {
					sorted.add(next);
					sortedSet.add(next.getName());
					iterator.remove();
				}
			}
			if (tables.size() > 0 && size == tables.size()) {
				iterator = tables.iterator();
				MetaTable next = iterator.next();
				sorted.add(next);
				sortedSet.add(next.getName());
				iterator.remove();
				Logger.getLogger(LangUtils.class.getName()).warning("***Possible error with circular link of " + tables);
			} else {
				size = tables.size();
			}
		}
		return sorted;
	}

	public static List<EntityBundle> sortBundlesByReferences(List<EntityBundle> bundles) {
		List<EntityBundle> sorted = new ArrayList<EntityBundle>();
		Set<String> namesSet = new HashSet<String>();
		Set<String> sortedSet = new HashSet<String>();
		Iterator<EntityBundle> iterator = bundles.iterator();
		while (iterator.hasNext()) {
			EntityBundle next = iterator.next();
			namesSet.add(next.entity);
			if (next.refEntityMap.size() == 0) {
				sorted.add(next);
				sortedSet.add(next.entity);
				iterator.remove();
			}
		}
		int size = bundles.size();
		while (bundles.size() > 0) {
			iterator = bundles.iterator();
			while (iterator.hasNext()) {
				EntityBundle next = iterator.next();
				boolean noneExistsKey = false;
				for (Entry<String, PersistReferenceAttribute> entry : next.refEntityMap.entrySet()) {
					String entity = entry.getValue().getEntity();
					if (!namesSet.contains(entity)) {
						continue;
					}
					if ((!sortedSet.contains(entity)) && (!entity.equals(next.entity))) {
						noneExistsKey = true;
						break;
					}
				}
				if (!noneExistsKey) {
					sorted.add(next);
					sortedSet.add(next.entity);
					iterator.remove();
				}
			}
			if (bundles.size() > 0 && size == bundles.size()) {
				iterator = bundles.iterator();
				EntityBundle next = iterator.next();
				sorted.add(next);
				sortedSet.add(next.entity);
				iterator.remove();
				Logger.getLogger(LangUtils.class.getName()).warning("***Possible error with circular link of " + bundles);
			} else {
				size = bundles.size();
			}
		}
		return sorted;
	}

	public static Class<? extends Object> getClazz(Object obj) throws ClassNotFoundException {
		Class<? extends Object> clazz = null;
		if (obj instanceof Class<?>) {
			clazz = (Class<?>) obj;
		} else if (obj instanceof String) {
			clazz = Class.forName((String) obj);
		} else {
			clazz = obj.getClass();
		}
		return clazz;
	}

	// public static Class<?> resolveClass(Field field, Class<?> returnType) {
	// Class<?> klazz = returnType;
	// if (Collection.class.isAssignableFrom(returnType)) {
	// klazz = getGenericClass(klazz, field.getGenericType());
	// } else if (Map.class.isAssignableFrom(returnType)) {
	// klazz = getGenericMappedClass(field.getGenericType());
	// } else if (returnType.isArray()) {
	// klazz = returnType.getComponentType();
	// }
	// return klazz;
	// }
	//
	// public static Class<?> resolveClass(Method method, Class<?> returnType) {
	// Class<?> klazz = returnType;
	// if (Collection.class.isAssignableFrom(returnType)) {
	// klazz = getGenericClass(klazz, method.getGenericReturnType());
	// } else if (Map.class.isAssignableFrom(returnType)) {
	// klazz = getGenericMappedClass(method.getGenericReturnType());
	// } else if (returnType.isArray()) {
	// klazz = returnType.getComponentType();
	// }
	// return klazz;
	// }
	public static Class<?> resolveClass(Field field, Class<?> returnType) {
		return getType(field.getGenericType(), returnType);
	}

	private static Class<?> getType(Type type, Class<?> returnType) {
		Class<?> klazz = returnType;
		if (Collection.class.isAssignableFrom(returnType)) {
			klazz = getGenericClass(returnType, type);
		} else if (Map.class.isAssignableFrom(returnType)) {
			klazz = getGenericMappedClass(type);
		} else if (returnType.isArray()) {
			klazz = returnType.getComponentType();
		}
		return klazz;
	}

	public static Class<?> resolveClass(Method method, Class<?> returnType) {
		return getType(method.getGenericReturnType(), returnType);
	}

	public static Class<?> getGenericClass(Class<?> clazz, Type genericReturnType) {
		String className = genericReturnType + "";
		int indexOf = className.indexOf("<");
		if (indexOf >= 0) {
			className = className.substring(indexOf + 1, className.indexOf(">"));
			try {
				return Class.forName(className.trim());
			} catch (ClassNotFoundException e) {
				throw new DataScooterException(e);
			}
		}
		return clazz;
	}

	public static Class<?> getGenericMappedClass(Type genericReturnType) {
		String className = genericReturnType + "";
		String[] array = className.substring(className.indexOf("<") + 1, className.indexOf(">")).split(",");
		try {
			return Class.forName(array[1].trim());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static final boolean equalsAnyWay(Object o1, Object o2) {
		if ((o1 == null) && (o2 != null)) {
			return false;
		} else if ((o1 != null) && (o2 == null)) {
			return false;
		} else if ((o1 == null) && (o2 == null)) {
			return true;
		} else {
			return o1.equals(o2);
		}
	}

	public static String formatSize(long longSize, int decimalPos) {
		NumberFormat fmt = NumberFormat.getNumberInstance();
		if (decimalPos >= 0) {
			fmt.setMaximumFractionDigits(decimalPos);
		}
		final double size = longSize;
		double val = size / (1024 * 1024 * 1024);
		if (val > 1) {
			return fmt.format(val).concat(" Gb");
		}
		val = size / (1024 * 1024);
		if (val > 1) {
			return fmt.format(val).concat(" MB");
		}
		val = size / 1024;
		if (val > 10) {
			return fmt.format(val).concat(" KB");
		}
		return fmt.format(val).concat(" bytes");
	}

	public static final boolean equalsAnyWay1(Object o1, Object o2) {
		if ((o1 == null) || (o2 == null)) {
			return false;
		} else {
			return o1.equals(o2);
		}
	}

	public static String entityName(Object obj) {
		if (obj == null) {
			throw new DataScooterException("***Null object");
		}
		if (obj instanceof String) {
			return obj.toString();
		} else if (obj instanceof Class<?>) {
			return ((Class<?>) obj).getName();
		} else {
			return obj.getClass().getName();
		}
	}

	@SuppressWarnings({
			"unchecked", "rawtypes"
	})
	public static final int compareAnyWay(Comparable o1, Comparable o2) {
		if ((o1 == null) && (o2 != null)) {
			return 1;
		} else if ((o1 != null) && (o2 == null)) {
			return -1;
		} else if ((o1 == null) && (o2 == null)) {
			return 0;
		} else if (o1 != null) {
			return o1.compareTo(o2);
		}
		return 0;
	}

	@SuppressWarnings({
			"unchecked", "rawtypes"
	})
	public static final int compareAnyWay(Object o1, Object o2) {
		if ((o1 == null) && (o2 != null)) {
			return 1;
		} else if ((o1 != null) && (o2 == null)) {
			return -1;
		} else if ((o1 == null) && (o2 == null)) {
			return 0;
		}
		if ((o1 instanceof Comparable) && (o2 instanceof Comparable)) {
			return ((Comparable) o1).compareTo((Comparable) o2);
		}
		if ((o1 != null) && (o2 != null)) {
			return o1.toString().compareTo(o2.toString());
		}
		return 0;
	}

	public static String defaultIfEmpty(Object value) {
		return defaultIfEmpty(value, "");
	}

	public static String defaultIfEmpty(Object value, String defaultString) {
		if (value == null) {
			return defaultString;
		}
		if (value.toString().trim().length() == 0) {
			return defaultString;
		}
		return value.toString();
	}

	public static boolean isEmptyString(Object value) {
		return (value == null) || (value.toString() == null) || (value.toString().trim().length() == 0) || (value.toString().equalsIgnoreCase("null"));
	}

	public static boolean isBooleanString(Object value, boolean defaultBoolean) {
		if (value == null || (value.toString() == null) || (value.toString().trim().length() == 0)) {
			return defaultBoolean;
		} else {
			return Boolean.valueOf(value.toString().toUpperCase());
		}
	}

	public static String abbreviate(String string, int maxLength, String delimiter, int partLength) {
		if (maxLength == 0) {
			return string;
		}
		if (string.length() <= maxLength) {
			return string;
		}
		StringBuilder result = new StringBuilder();
		if ((delimiter != null) && string.contains(delimiter)) {
			String[] split = string.split(delimiter);
			for (String str : split) {
				if (str.length() > 0) {
					if (str.length() > partLength) {
						if (result.length() > 0) {
							result.append(delimiter);
						}
						result.append(str.substring(0, partLength));
					} else {
						result.append(str);
					}
				}
			}
		} else {
			char[] charArray = string.toCharArray();
			for (int a = 0; a < charArray.length; a++) {
				Character character = charArray[a];
				if (Character.isUpperCase(character)) {
					if (result.length() > 0) {
						result.append(delimiter);
					}
					int endIndex = a + 1 + partLength;
					if (endIndex > string.length()) {
						endIndex = string.length();
					}
					result.append(character + string.substring(a + 1, endIndex));
					if (result.length() > maxLength) {
						break;
					}
				}
			}
		}
		if (result.length() == 0) {
			result = result.append((string));
		}
		if (result.length() > maxLength) {
			result.append(result.substring(0, maxLength));
		}
		return result.toString().toUpperCase();
	}

	public static String shrink(String string, int maxLength) {
		if (string.length() > maxLength) {
			return string.substring(0, maxLength);
		}
		return string;
	}

	public static String getRandomIndexString(int lenght) {
		String res = String.valueOf(RANDOM.nextInt());
		indexCount++;
		if (res.length() > lenght) {
			return SnipUtils.UNDERLINE + indexCount + SnipUtils.UNDERLINE + res.substring(res.length() - lenght, res.length());
		} else {
			return SnipUtils.UNDERLINE + indexCount + SnipUtils.UNDERLINE + res;
		}
	}

	public static String getRandStr(int lenght) {
		String res = String.valueOf(RANDOM.nextInt());
		return res.substring(res.length() - lenght, res.length()); //$NON-NLS-1$
	}

	public static int getRandom() {
		return RANDOM.nextInt();
	}

	@SuppressWarnings("nls")
	public static Boolean parseToBoolean(String str) {
		if ((str != null) && (str.length() > 0)) {
			if (str.equalsIgnoreCase("0")) {
				return Boolean.FALSE;
			}
			if (str.equalsIgnoreCase("1")) {
				return Boolean.TRUE;
			}
			if (str.equalsIgnoreCase("true")) {
				return Boolean.TRUE;
			}
			if (str.equalsIgnoreCase("false")) {
				return Boolean.FALSE;
			}
		}
		return Boolean.FALSE;
	}

	@SuppressWarnings("nls")
	public static Integer parseToInteger(String str) {
		if ((str != null) && (str.length() > 0)) {
			try {
				return Integer.valueOf(str);
			} catch (NumberFormatException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	public static String repeat(String str, int count) {
		StringBuilder builder = new StringBuilder();
		for (int a = 0; a < count; a++) {
			builder.append(str);
		}
		return builder.toString();
	}

	public static String capitalize(String str) {
		int strLen;
		if (str == null || (strLen = str.length()) == 0) {
			return str;
		}
		return new StringBuffer(strLen).append(Character.toTitleCase(str.charAt(0))).append(str.substring(1)).toString();
	}

	public static String uncapitalize(String str) {
		int strLen;
		if (str == null || (strLen = str.length()) == 0) {
			return str;
		}
		return new StringBuffer(strLen).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString();
	}

	public static String prepareName(String str) {
		int strLen;
		if (str == null || (strLen = str.length()) == 0) {
			return str;
		}
		String shortStr = str.replaceFirst(GET, "").replaceFirst(SET, "").replaceFirst(ADD, "").replaceFirst(DELETE, "").replaceFirst(REMOVE, "");
		strLen = shortStr.length();
		if (strLen == 0) {
			return str;
		}
		return new StringBuffer(strLen).append(Character.toLowerCase(shortStr.charAt(0))).append(shortStr.substring(1)).toString();
	}

	public static String underlineName(String str) {
		StringBuilder builder = new StringBuilder();
		int index = 0;
		for (char ch : str.toCharArray()) {
			if ((Character.getType(ch) == Character.UPPERCASE_LETTER)) {
				if (index != 0) {
					builder.append('_');
				}
				builder.append(Character.toLowerCase(ch));
			} else if (Character.isSpaceChar(ch)) {
				builder.append('_');
			} else {
				builder.append(ch);
			}
			index++;
		}
		return builder.toString();
	}

	public static List<String> enumToList(Enum<?>... ee) {
		List<String> result = new ArrayList<String>();
		for (Enum<?> obj : ee) {
			result.add(obj.name());
		}
		return result;
	}

	public static String overlay(String str, String overlay, int start, int end) {
		if (str == null) {
			return null;
		}
		if (overlay == null) {
			overlay = "";
		}
		int len = str.length();
		if (start < 0) {
			start = 0;
		}
		if (start > len) {
			start = len;
		}
		if (end < 0) {
			end = 0;
		}
		if (end > len) {
			end = len;
		}
		if (start > end) {
			int temp = start;
			start = end;
			end = temp;
		}
		return new StringBuffer(len + start - end + overlay.length() + 1).append(str.substring(0, start)).append(overlay).append(str.substring(end)).toString();
	}

	public static String reverse(String str) {
		if (str == null) {
			return null;
		}
		return new StringBuffer(str).reverse().toString();
	}

	public static boolean like(Object o1, Object o2) {
		if ((o1 == null) && (o2 != null)) {
			return false;
		} else if ((o1 != null) && (o2 == null)) {
			return false;
		} else if ((o1 == null) && (o2 == null)) {
			return true;
		} else if (o1 != null) {
			return o1.toString().contains(o2.toString());
		}
		return false;
	}

	public static void removeDir(File file) {
		clearDir(file);
		if (file.exists()) {
			if (!file.delete()) {
				throw new DataScooterException("***Unable to delete file: " + file.getPath());
			}
		}
	}

	public static void clearDir(File file) {
		if (file.exists()) {
			for (File item : file.listFiles()) {
				if (item.isDirectory()) {
					clearDir(item);
				}
				if (!item.delete()) {
					throw new DataScooterException("***Unable to delete file: " + item.getPath());
				}
			}
		}
	}

	public static String getDefault(Object name) {
		return name == null ? "" : name.toString();
	}
}
